/*
@auther fenglei
*/
package aliyun_oss

import (
	"context"
	"fmt"
	"io"
	"os"
	"sort"
	"strings"
	"time"

	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
	"github.com/avast/retry-go"
)

// SlsOptions defines options.
type OssOptions struct {
	YunAccessKeyId     string
	YunAccessKeySecret string
	Region             string //cn-beijing
	Endpoint           bool   //true为内网
	RetryCount         int    //重试次数
	// +optional
	//LogLevel logger.LogLevel
}
type OssDao struct {
	client *oss.Client
}

func NewOss(opts *OssOptions) (*OssDao, error) {
	opts, err := setOssDefaults(opts)
	if err != nil {
		return nil, err
	}
	provider := credentials.NewStaticCredentialsProvider(opts.YunAccessKeyId, opts.YunAccessKeySecret)
	cfg := oss.LoadDefaultConfig().WithCredentialsProvider(provider)
	cfg.WithRegion(opts.Region)
	//日志
	//cfg.WithLogLevel(oss.LogError).WithLogPrinter(oss.LogPrinterFunc(func(a ...any) {
	//	fmt.Fprint(os.Stderr, a...)
	//}))
	if opts.Endpoint {
		cfg.WithUseInternalEndpoint(true)
	} else {
		cfg.WithUseAccelerateEndpoint(true)
	}
	//重试次数
	cfg.WithRetryMaxAttempts(opts.RetryCount)
	return &OssDao{
		client: oss.NewClient(cfg),
	}, nil
}
func (e *OssDao) ListObjects(bucketName string, prefix string) ([]OssFileList, error) {
	ossList := make([]OssFileList, 0)
	p := e.client.NewListObjectsV2Paginator(&oss.ListObjectsV2Request{
		Bucket:    oss.Ptr(bucketName),
		Prefix:    oss.Ptr(prefix),
		Delimiter: oss.Ptr("/"),
	})
	for p.HasNext() {
		page, err := p.NextPage(context.TODO())
		if err != nil {
			return nil, err
		}
		for _, obj := range page.Contents {
			if oss.ToString(obj.Key) == prefix {
				continue
			}
			ossList = append(ossList, OssFileList{Key: oss.ToString(obj.Key), Size: obj.Size, LastModified: oss.ToTime(obj.LastModified), IsFile: true})
		}
		for _, v := range page.CommonPrefixes {
			ossList = append(ossList, OssFileList{Key: oss.ToString(v.Prefix), IsFile: false})
		}
	}
	sort.Slice(ossList, func(i, j int) bool {
		return ossList[i].Size < ossList[j].Size
	})
	return ossList, nil
}

// 删除文件
func (e *OssDao) DeleteObject(bucketName string, key string) (*oss.DeleteObjectResult, error) {
	request := &oss.DeleteObjectRequest{
		Bucket: oss.Ptr(bucketName),
		Key:    oss.Ptr(key),
	}
	result, err := e.client.DeleteObject(context.TODO(), request)
	if err != nil {
		return nil, err
	}
	return result, nil
}

// 创建目录
func (e *OssDao) MkDir(bucketName string, key string) error {
	request := &oss.PutObjectRequest{
		Bucket: oss.Ptr(bucketName),
		Key:    oss.Ptr(key),
		Body:   strings.NewReader(""),
	}

	_, err := e.client.PutObject(context.TODO(), request)
	if err != nil {
		return err
	}
	return nil
}

// 上传重试
func (e *OssDao) PutObjectRetry(bucketName, objectName, localFile string, attempts uint, delay time.Duration) (err error) {
	retryFunc := func() error {
		err = e.PutObjectPart(bucketName, objectName, localFile)
		return err
	}
	// 设置重试选项
	opts := []retry.Option{
		retry.Attempts(attempts), // 最多重试3次
		retry.Delay(delay),       // 每次重试间隔10秒
	}
	// 执行重试操作
	_ = retry.Do(retryFunc, opts...)
	return err
}

// PutObjectPart 分片上传文件
func (e *OssDao) PutObjectPart(bucketName string, objectName string, filePath string) error {
	// 创建上传器
	u := e.client.NewUploader(func(uo *oss.UploaderOptions) {
		uo.PartSize = 1024 * 1024 * 5 // 设置分片大小为5MB
		uo.ParallelNum = 5            // 设置并行上传数为5
	})
	// 执行上传文件的操作
	_, err := u.UploadFile(context.TODO(),
		&oss.PutObjectRequest{
			Bucket: oss.Ptr(bucketName),
			Key:    oss.Ptr(objectName)},
		filePath)
	if err != nil {
		return err
	}
	return nil
}

// 添加下载重试
func (e *OssDao) DownFileRetry(bucketName, objectName, localFile string, attempts uint, delay time.Duration) (err error) {
	retryFunc := func() error {
		err = e.DownFile(bucketName, objectName, localFile)
		return err
	}
	// 设置重试选项
	opts := []retry.Option{
		retry.Attempts(attempts), // 最多重试3次
		retry.Delay(delay),       // 每次重试间隔10秒
	}
	// 执行重试操作
	_ = retry.Do(retryFunc, opts...)
	return err
}

// 下载文件
func (e *OssDao) DownFile(bucketName, objectName, localFile string) error {
	// 创建获取对象的请求
	request := &oss.GetObjectRequest{
		Bucket: oss.Ptr(bucketName), // 存储空间名称
		Key:    oss.Ptr(objectName), // 对象名称
	}

	// 执行获取对象的操作并处理结果
	result, err := e.client.GetObject(context.TODO(), request)
	if err != nil {
		return err
	}
	defer result.Body.Close() // 确保在函数结束时关闭响应体

	// 一次性读取整个文件内容
	data, err := io.ReadAll(result.Body)
	if err != nil {
		return err
	}
	// 将内容写入到文件
	err = os.WriteFile(localFile, data, 0644)
	if err != nil {
		return err
	}
	return nil
}
func (e *OssDao) DownloadFile(bucketName, objectName, localFile string, partSize int64, parallelNum int) (*oss.DownloadResult, error) {
	// 创建获取对象的请求
	request := &oss.GetObjectRequest{
		Bucket: oss.Ptr(bucketName), // 存储空间名称
		Key:    oss.Ptr(objectName), // 对象名称
	}
	d := e.client.NewDownloader()
	// 执行下载文件的请求
	result, err := d.DownloadFile(context.TODO(), request, localFile, func(do *oss.DownloaderOptions) {
		do.PartSize = partSize
		do.VerifyData = true
		do.ParallelNum = parallelNum
	})
	if err != nil {
		return nil, err
	}
	return result, nil
}

// 判断文件是否存在
func (e *OssDao) IsExist(bucketName, objectName string) (bool, error) {
	isExist, err := e.client.IsObjectExist(context.TODO(), bucketName, objectName)
	if err != nil {
		return false, err
	}
	return isExist, nil
}

// 获取上传或下载签名
func (e *OssDao) SignURL(bucketName, objectName string, timeOut time.Duration) (string, error) {
	// 生成GetObject的预签名URL
	result, err := e.client.Presign(context.TODO(), &oss.GetObjectRequest{
		Bucket: oss.Ptr(bucketName),
		Key:    oss.Ptr(objectName),
	},
		oss.PresignExpires(timeOut),
	)
	if err != nil {
		return "", err
	}
	return result.URL, nil
}

func setOssDefaults(opts *OssOptions) (*OssOptions, error) {
	if opts.YunAccessKeySecret == "" {
		return nil, fmt.Errorf("YunAccessKeySecret is nil")
	}
	if opts.YunAccessKeyId == "" {
		return nil, fmt.Errorf("YunAccessKeyId is nil")
	}
	if opts.RetryCount <= 0 {
		opts.RetryCount = 3
	}
	if opts.Region == "" {
		opts.Region = "cn-beijing"
	}
	return opts, nil
}
